# COMPI: Communication + MPI
cmake_minimum_required(VERSION 3.20)
project(COMPI)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# 创建库目标
set(COMPI_SOURCES
  src/p2p.cpp
  src/context.cpp
  src/tag.cpp
  src/channel.cpp
  src/resource_manager.cpp
  src/topology.cpp
)

add_library(compi ${COMPI_SOURCES})

# 头文件路径
target_include_directories(compi
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/contrib/serializer/include>
)

# C++ 标准与可见性
set_target_properties(compi PROPERTIES
  CXX_STANDARD 17
  CXX_STANDARD_REQUIRED ON
  POSITION_INDEPENDENT_CODE ON
  OUTPUT_NAME compi
)

# 依赖：必须找到 MPI，否则中断配置
find_package(MPI REQUIRED)
if(NOT MPI_FOUND)
  message(FATAL_ERROR "MPI is required but was not found.")
endif()
add_subdirectory(contrib/serializer)

target_link_libraries(compi PUBLIC MPI::MPI_CXX serializer)

# 示例程序
add_executable(ping_pong examples/ping_pong.cpp)
target_link_libraries(ping_pong PRIVATE compi)
# 使示例以 C++17 编译，满足 concepts 等新特性
set_target_properties(ping_pong PROPERTIES
  CXX_STANDARD 17
  CXX_STANDARD_REQUIRED ON
)

# 关系演示示例：请求层与传输分层
add_executable(layers_demo examples/layers_demo.cpp)
target_link_libraries(layers_demo PRIVATE compi)
set_target_properties(layers_demo PROPERTIES
  CXX_STANDARD 17
  CXX_STANDARD_REQUIRED ON
)

# Channel 全功能综合示例
add_executable(channel_showcase examples/channel_showcase.cpp)
target_link_libraries(channel_showcase PRIVATE compi)
set_target_properties(channel_showcase PROPERTIES
  CXX_STANDARD 17
  CXX_STANDARD_REQUIRED ON
)

# TypedChannel 点对点示例
add_executable(typed_channel_ping_pong examples/typed_channel_ping_pong.cpp)
target_link_libraries(typed_channel_ping_pong PRIVATE compi)
  set_target_properties(typed_channel_ping_pong PROPERTIES
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON
  )

  # Collectives benchmark
  add_executable(collectives_bench examples/collectives_bench.cpp)
  target_link_libraries(collectives_bench PRIVATE compi)
  set_target_properties(collectives_bench PROPERTIES
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON
  )

add_executable(topology_cart examples/topology_cart.cpp)
target_link_libraries(topology_cart PRIVATE compi)
set_target_properties(topology_cart PROPERTIES
  CXX_STANDARD 17
  CXX_STANDARD_REQUIRED ON
)

add_executable(topology_graph examples/topology_graph.cpp)
target_link_libraries(topology_graph PRIVATE compi)
set_target_properties(topology_graph PROPERTIES
  CXX_STANDARD 17
  CXX_STANDARD_REQUIRED ON
)

# 测试选项：但若未找到 GTest 则自动跳过
option(COMPI_BUILD_TESTS "Build COMPI tests" OFF)
if(COMPI_BUILD_TESTS)
    enable_testing()
    # 兼容 CONFIG 与常规查找；允许使用环境变量注入 CMAKE_PREFIX_PATH（与顶层策略一致）
    if(NOT DEFINED ENV{CMAKE_PREFIX_PATH})
        # 可按需覆盖为你的 GTest 安装路径
        set(ENV{CMAKE_PREFIX_PATH} "/home/one/app/googletest-1.17.0/lib/cmake")
    endif()
    find_package(GTest CONFIG QUIET)
    if(NOT GTest_FOUND)
        find_package(GTest QUIET)
    endif()
    if(GTest_FOUND)
        # 设置 GTest 目标
        set(_gtest_main_target "")
        if(TARGET GTest::gtest_main)
            set(_gtest_main_target GTest::gtest_main)
        elseif(TARGET GTest::Main)
            set(_gtest_main_target GTest::Main)
        elseif(TARGET gtest_main)
            set(_gtest_main_target gtest_main)
        endif()
        if(NOT _gtest_main_target)
            if(DEFINED GTEST_MAIN_LIBRARIES OR DEFINED GTEST_LIBRARIES)
                set(_gtest_main_target "${GTEST_MAIN_LIBRARIES} ${GTEST_LIBRARIES}")
            else()
                message(FATAL_ERROR "GTest 已找到但未导出可用目标或变量，请检查 GTest 安装。")
            endif()
        endif()

        function(add_compi_test target_name source_relpath)
            add_executable(${target_name} ${source_relpath} tests/test_main.cpp)
            set_target_properties(${target_name} PROPERTIES
                CXX_STANDARD 23
                CXX_STANDARD_REQUIRED ON
            )
            # 使用 GTest::gtest 而不是 gtest_main，因为我们提供了自己的 main 函数
            if(TARGET GTest::gtest)
                target_link_libraries(${target_name} PRIVATE compi GTest::gtest)
            elseif(TARGET GTest::GTest)
                target_link_libraries(${target_name} PRIVATE compi GTest::GTest)
            else()
                target_link_libraries(${target_name} PRIVATE compi ${GTEST_LIBRARIES})
            endif()
            target_include_directories(${target_name} PRIVATE
                ${CMAKE_CURRENT_SOURCE_DIR}/include
                ${CMAKE_SOURCE_DIR}/contrib/serializer/include
            )
            add_test(NAME ${target_name} COMMAND ${target_name})
            set_tests_properties(${target_name} PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
        endfunction()

        # 多进程 MPI 测试注册，使用 mpiexec -n <procs>
        function(add_compi_mpi_test target_name source_relpath)
            set(options)
            set(oneValueArgs NUM_PROCS)
            set(multiValueArgs)
            cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

            if(NOT ARG_NUM_PROCS)
                set(ARG_NUM_PROCS 4)
            endif()

            add_executable(${target_name} ${source_relpath})
            set_target_properties(${target_name} PROPERTIES
                CXX_STANDARD 23
                CXX_STANDARD_REQUIRED ON
            )
            target_link_libraries(${target_name} PRIVATE compi ${_gtest_main_target})
            target_include_directories(${target_name} PRIVATE
                ${CMAKE_CURRENT_SOURCE_DIR}/include
                ${CMAKE_SOURCE_DIR}/contrib/serializer/include
            )
            add_test(NAME ${target_name}
                     COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${ARG_NUM_PROCS} $<TARGET_FILE:${target_name}>)
            set_tests_properties(${target_name} PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
        endfunction()

        # 添加 COMPI 测试
        add_compi_test(compi_test_tag tests/test_tag.cpp)
        add_compi_test(compi_test_context tests/test_context.cpp)
        add_compi_test(compi_test_packed_range tests/test_packed_range.cpp)
        add_compi_test(compi_test_channel tests/test_channel.cpp)
        # TagLease RAII 测试
        add_compi_test(compi_test_tag_lease tests/test_tag_lease.cpp)
        add_compi_mpi_test(compi_test_collectives tests/test_collectives.cpp NUM_PROCS 4)
        add_compi_test(compi_test_serializer_stream tests/test_serializer_stream.cpp)
        add_compi_mpi_test(compi_test_sync_strategies tests/test_sync_strategies.cpp NUM_PROCS 4)
        add_compi_test(compi_test_error_paths tests/test_error_paths.cpp)
        add_compi_test(compi_test_unified_error_handling tests/test_unified_error_handling.cpp)
        # Status/StatusOr 与非抛出等待的单元测试
        add_compi_test(compi_test_status tests/test_status.cpp)

        # TagLease 适配的传输层端到端测试（需要至少 2 个进程）
        add_compi_mpi_test(compi_test_transport_taglease tests/test_transport_taglease.cpp NUM_PROCS 2)

        # ANY_TAG 并发竞争测试：多进程运行
        add_compi_mpi_test(compi_test_any_tag_concurrency tests/test_any_tag_concurrency.cpp NUM_PROCS 4)

        # 取消 / 轮询 / 请求组 组合场景测试：多进程运行
        add_compi_mpi_test(compi_test_cancel_poll_group tests/test_cancel_poll_group.cpp NUM_PROCS 4)

    else()
        message(STATUS "GTest 未找到，跳过 COMPI 测试构建")
    endif()
endif()
